<?php

namespace CrontabConsole;

use Exception;
use Yii;
use yii\console\ExitCode;

class ProcWorker {

    public static $tasks = [];
 
    public static $driver;

    private static $procPoll = []; // 允许的进程列表

    private static $pipe = null; //通道
    
    private static $script = 'yii'; // yii脚本路径

    private static $scriptSupport = [
        'yii' => '@app/yii',
        'think' => '@app/think'
    ];

    // 是否重定向输出
    public static $restd = true;
    // 重定向错误
    public static $stderr = '@runtime/crontab/{date}-error.log';
    // 重定向输出
    public static $stdout = '@runtime/crontab/{date}-output.log';

    /**
     * 设置默认调度的脚本
     */
    public static function setScript($script) 
    {
        if (!isset(self::$scriptSupport[$script])) {
            throw new Exception('non-support defaultScript  => ' . $script);
        }

        $scriptFile = Yii::getAlias(self::$scriptSupport[$script]);
        if (!is_file($scriptFile)) {
            throw new Exception('No file found  => ' . $scriptFile);
        }

        self::$script = $script;
    }

    /**
     * 启动子进程,运行脚本
     * @return 返回执行完毕的任务
     */
    public static function run() 
    {
        $nowDatetime = date('Y-m-d H:i');
        $startExectime = self::getCurrentTime();

        $descriptorspec = array(
            0 => array("pipe", "a"),        // 标准输入，子进程从此管道中读取数据
        );

        if (self::$restd) {
            if (self::$stdout) {
                $descriptorspec[1] = ['file', Yii::getAlias(self::interpolate(self::$stdout, ['date' => date('Y-m-d')])),'ab'];
            }
    
            if (self::$stderr) {
                $descriptorspec[2] = ['file', Yii::getAlias(self::interpolate(self::$stderr, ['date' => date('Y-m-d')])),'a'];
            }
        }


        foreach (self::$tasks as $id => $task) {
            $task->status = 1; //运行中
            // 乐观锁方式更新, 保证任务在并发下, 只会运行一次
            $isOk = self::$driver->updateOne($task);
            if (!$isOk) {
                unset(self::$tasks[$id]);
            }
        }

        foreach (self::$tasks as $id => $task) {
            $cmd = 'php ' . Yii::getAlias(self::$scriptSupport[self::$script]) . " {$task->route}";
            self::$procPoll[$task->id] = proc_open($cmd, $descriptorspec, self::$pipe);
        }

        while (count(self::$procPoll)) {
            foreach (self::$procPoll as $id => $result) {
                $etat = proc_get_status($result);
                if ($etat['running'] === false) {
                    proc_close($result);
                    unset(self::$procPoll[$id]);

                    self::$tasks[$id]->calcNextRunDatetime(); // 计算下次运行时间
                    self::$tasks[$id]->status = 0;
                    self::$tasks[$id]->count++;
                    self::$tasks[$id]->last_rundate = $nowDatetime;
                    self::$tasks[$id]->exectime = abs(sprintf('%.2f', self::getCurrentTime() - $startExectime) - 1);

                    if ($etat['exitcode'] !== ExitCode::OK) {
                        self::$tasks[$id]->status = 2;
                    }

                    self::$driver->updateOne(self::$tasks[$id]);
                }
            }
        }
        return self::$tasks;
    }

    public static function getCurrentTime()
    {  
        list ($msec, $sec) = explode(" ", microtime());  
        return (float)$msec + (float)$sec;  
    }


    public static function interpolate($message, array $context = array())
    {
        // build a replacement array with braces around the context keys
        $replace = array();
        foreach ($context as $key => $val) {
            // check that the value can be casted to string
            if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
                $replace['{' . $key . '}'] = $val;
            }
        }

        // interpolate replacement values into the message and return
        return strtr($message, $replace);
    }

}